#ifndef NOSAL_CORE_TEST_TYPES_H
#define NOSAL_CORE_TEST_TYPES_H

#include <type_traits>

// This file defines a number of data types with differing properties
// that make them "interesting" for testing wrapper types such as e.g.
// nosal::core::Result and nosal::core::Optional.

// A type that is trivial
struct Trivial final
{
    ~Trivial() = default;

    int ma;
};

static constexpr inline bool operator==(Trivial const& lhs, Trivial const& rhs) noexcept
{
    return lhs.ma == rhs.ma;
}

static_assert(std::is_destructible<Trivial>::value, "must be destructible");
static_assert(std::is_copy_constructible<Trivial>::value, "must be copyable");
static_assert(std::is_copy_assignable<Trivial>::value, "must be copyable");
static_assert(std::is_move_constructible<Trivial>::value, "must be moveable");
static_assert(std::is_move_assignable<Trivial>::value, "must be moveable");

static_assert(std::is_trivially_destructible<Trivial>::value, "must be trivially destructible");
static_assert(std::is_trivially_copyable<Trivial>::value, "must be trivially copyable");
static_assert(std::is_trivially_copy_constructible<Trivial>::value, "must be trivially copy constructible");
static_assert(std::is_trivially_copy_assignable<Trivial>::value, "must be trivially copy assignable");
static_assert(std::is_trivially_move_constructible<Trivial>::value, "must be trivially move constructible");
static_assert(std::is_trivially_move_assignable<Trivial>::value, "must be trivially move assignable");
static_assert(std::is_trivial<Trivial>::value, "must be a trivial type");
static_assert(std::is_literal_type<Trivial>::value, "must be a literal type");
static_assert(std::is_standard_layout<Trivial>::value, "must have standard layout");

// -------------------------------------------------------------------------

// A type that is literal, but not trivial
struct Literal final
{
    constexpr explicit Literal(int a) noexcept
        : ma(a)
    { }

    int ma;
};

static constexpr inline bool operator==(Literal const& lhs, Literal const& rhs) noexcept
{
    return lhs.ma == rhs.ma;
}

static_assert(std::is_destructible<Literal>::value, "must be destructible");
static_assert(std::is_copy_constructible<Literal>::value, "must be copyable");
static_assert(std::is_copy_assignable<Literal>::value, "must be copyable");
static_assert(std::is_move_constructible<Literal>::value, "must be moveable");
static_assert(std::is_move_assignable<Literal>::value, "must be moveable");

static_assert(std::is_trivially_destructible<Literal>::value, "must be trivially destructible");
static_assert(std::is_trivially_copyable<Literal>::value, "must be trivially copyable");
static_assert(std::is_trivially_copy_constructible<Literal>::value, "must be trivially copy constructible");
static_assert(std::is_trivially_copy_assignable<Literal>::value, "must be trivially copy assignable");
static_assert(std::is_trivially_move_constructible<Literal>::value, "must be trivially move constructible");
static_assert(std::is_trivially_move_assignable<Literal>::value, "must be trivially move assignable");
static_assert(!std::is_trivial<Literal>::value, "must not be a trivial type");
static_assert(std::is_literal_type<Literal>::value, "must be a literal type");
static_assert(std::is_standard_layout<Literal>::value, "must have standard layout");

// -------------------------------------------------------------------------

// A literal type that is non-copyable, but moveable (though not trivially-moveable)
struct LiteralNonCopyable final
{
    constexpr explicit LiteralNonCopyable(int a)
        : ma(a)
    { }

    LiteralNonCopyable(LiteralNonCopyable const&) = delete;
    LiteralNonCopyable& operator=(LiteralNonCopyable const&) = delete;

    LiteralNonCopyable(LiteralNonCopyable&& other) noexcept
        : ma(other.ma)
    { }
    LiteralNonCopyable& operator=(LiteralNonCopyable&& other) noexcept
    {
        ma = other.ma;
        return *this;
    }

    int ma;
};

static constexpr inline bool operator==(LiteralNonCopyable const& lhs, LiteralNonCopyable const& rhs) noexcept
{
    return lhs.ma == rhs.ma;
}

static_assert(std::is_destructible<LiteralNonCopyable>::value, "must be destructible");
static_assert(!std::is_copy_constructible<LiteralNonCopyable>::value, "must not be copyable");
static_assert(!std::is_copy_assignable<LiteralNonCopyable>::value, "must not be copyable");
static_assert(std::is_move_constructible<LiteralNonCopyable>::value, "must be moveable");
static_assert(std::is_move_assignable<LiteralNonCopyable>::value, "must be moveable");

static_assert(std::is_trivially_destructible<LiteralNonCopyable>::value, "must be trivially destructible");
static_assert(!std::is_trivially_copyable<LiteralNonCopyable>::value, "must not be trivially copyable");
static_assert(!std::is_trivially_copy_constructible<LiteralNonCopyable>::value,
    "must not be trivially copy constructible");
static_assert(!std::is_trivially_copy_assignable<LiteralNonCopyable>::value, "must not be trivially copy assignable");
static_assert(!std::is_trivially_move_constructible<LiteralNonCopyable>::value,
    "must not be trivially move constructible");
static_assert(!std::is_trivially_move_assignable<LiteralNonCopyable>::value, "must not be trivially move assignable");
static_assert(!std::is_trivial<LiteralNonCopyable>::value, "must not be a trivial type");
static_assert(std::is_literal_type<LiteralNonCopyable>::value, "must be a literal type");
static_assert(std::is_standard_layout<LiteralNonCopyable>::value, "must have standard layout");

// -------------------------------------------------------------------------

// A non-literal type that is copyable and moveable
struct NonLiteral final
{
    explicit NonLiteral(int a) noexcept
        : ma(a)
    { }
    ~NonLiteral() noexcept
    { }

    NonLiteral(NonLiteral const& other)
        : ma(other.ma)
    { }
    NonLiteral& operator=(NonLiteral const& other)
    {
        ma = other.ma;
        return *this;
    }

    NonLiteral(NonLiteral&& other) noexcept
        : ma(other.ma)
    { }
    NonLiteral& operator=(NonLiteral&& other) noexcept
    {
        ma = other.ma;
        return *this;
    }

    int ma;
};

static inline bool operator==(NonLiteral const& lhs, NonLiteral const& rhs) noexcept
{
    return lhs.ma == rhs.ma;
}

static_assert(std::is_destructible<NonLiteral>::value, "must be destructible");
static_assert(std::is_copy_constructible<NonLiteral>::value, "must be copyable");
static_assert(std::is_copy_assignable<NonLiteral>::value, "must be copyable");
static_assert(std::is_move_constructible<NonLiteral>::value, "must be moveable");
static_assert(std::is_move_assignable<NonLiteral>::value, "must be moveable");

static_assert(!std::is_trivially_destructible<NonLiteral>::value, "must not be trivially destructible");
static_assert(!std::is_trivially_copyable<NonLiteral>::value, "must not be trivially copyable");
static_assert(!std::is_trivially_copy_constructible<NonLiteral>::value, "must not be trivially copy constructible");
static_assert(!std::is_trivially_copy_assignable<NonLiteral>::value, "must not be trivially copy assignable");
static_assert(!std::is_trivially_move_constructible<NonLiteral>::value, "must not be trivially move constructible");
static_assert(!std::is_trivially_move_assignable<NonLiteral>::value, "must not be trivially move assignable");
static_assert(!std::is_trivial<NonLiteral>::value, "must not be a trivial type");
static_assert(!std::is_literal_type<NonLiteral>::value, "must not be a literal type");
static_assert(std::is_standard_layout<NonLiteral>::value, "must have standard layout");

// -------------------------------------------------------------------------

// A non-literal type that is non-copyable but moveable
struct NonCopyable final
{
    explicit NonCopyable(int a) noexcept
        : ma(a)
    { }
    ~NonCopyable() noexcept
    { }

    NonCopyable(NonCopyable const& other) = delete;
    NonCopyable& operator=(NonCopyable const& other) = delete;

    NonCopyable(NonCopyable&& other) noexcept
        : ma(other.ma)
    { }
    NonCopyable& operator=(NonCopyable&& other) noexcept
    {
        ma = other.ma;
        return *this;
    }

    int ma;
};

static inline bool operator==(NonCopyable const& lhs, NonCopyable const& rhs) noexcept
{
    return lhs.ma == rhs.ma;
}

static_assert(std::is_destructible<NonCopyable>::value, "must be destructible");
static_assert(!std::is_copy_constructible<NonCopyable>::value, "must not be copyable");
static_assert(!std::is_copy_assignable<NonCopyable>::value, "must not be copyable");
static_assert(std::is_move_constructible<NonCopyable>::value, "must be moveable");
static_assert(std::is_move_assignable<NonCopyable>::value, "must be moveable");

static_assert(!std::is_trivially_destructible<NonCopyable>::value, "must not be trivially destructible");
static_assert(!std::is_trivially_copyable<NonCopyable>::value, "must not be trivially copyable");
static_assert(!std::is_trivially_copy_constructible<NonCopyable>::value, "must not be trivially copy constructible");
static_assert(!std::is_trivially_copy_assignable<NonCopyable>::value, "must not be trivially copy assignable");
static_assert(!std::is_trivially_move_constructible<NonCopyable>::value, "must not be trivially move constructible");
static_assert(!std::is_trivially_move_assignable<NonCopyable>::value, "must not be trivially move assignable");
static_assert(!std::is_trivial<NonCopyable>::value, "must not be a trivial type");
static_assert(!std::is_literal_type<NonCopyable>::value, "must not be a literal type");
static_assert(std::is_standard_layout<NonCopyable>::value, "must have standard layout");

// -------------------------------------------------------------------------

// A non-literal type that is non-copyable and non-moveable
struct NonMoveable final
{
    explicit NonMoveable(int a) noexcept
        : ma(a)
    { }
    ~NonMoveable() noexcept
    { }

    NonMoveable(NonMoveable const& other) = delete;
    NonMoveable& operator=(NonMoveable const& other) = delete;

    NonMoveable(NonMoveable&& other) = delete;
    NonMoveable& operator=(NonMoveable&& other) = delete;

    int ma;
};

static inline bool operator==(NonMoveable const& lhs, NonMoveable const& rhs) noexcept
{
    return lhs.ma == rhs.ma;
}

static_assert(std::is_destructible<NonMoveable>::value, "must be destructible");
static_assert(!std::is_copy_constructible<NonMoveable>::value, "must not be copyable");
static_assert(!std::is_copy_assignable<NonMoveable>::value, "must not be copyable");
static_assert(!std::is_move_constructible<NonMoveable>::value, "must not be moveable");
static_assert(!std::is_move_assignable<NonMoveable>::value, "must not be moveable");

static_assert(!std::is_trivially_destructible<NonMoveable>::value, "must not be trivially destructible");
static_assert(!std::is_trivially_copyable<NonMoveable>::value, "must not be trivially copyable");
static_assert(!std::is_trivially_copy_constructible<NonMoveable>::value, "must not be trivially copy constructible");
static_assert(!std::is_trivially_copy_assignable<NonMoveable>::value, "must not be trivially copy assignable");
static_assert(!std::is_trivially_move_constructible<NonMoveable>::value, "must not be trivially move constructible");
static_assert(!std::is_trivially_move_assignable<NonMoveable>::value, "must not be trivially move assignable");
static_assert(!std::is_trivial<NonMoveable>::value, "must not be a trivial type");
static_assert(!std::is_literal_type<NonMoveable>::value, "must not be a literal type");
static_assert(std::is_standard_layout<NonMoveable>::value, "must have standard layout");

// -------------------------------------------------------------------------

// A non-literal type that is copyable but non-moveable
// (this hardly makes any sense in practice, but uncovers wrong behavior of both nosal::core::Result and std::optional)
struct CopyableButNotMoveable final
{
    explicit CopyableButNotMoveable(int a) noexcept
        : ma(a)
    { }
    ~CopyableButNotMoveable() noexcept
    { }

    CopyableButNotMoveable(CopyableButNotMoveable const& other)
        : ma(other.ma)
    { }
    CopyableButNotMoveable& operator=(CopyableButNotMoveable const& other)
    {
        ma = other.ma;
        return *this;
    }

    CopyableButNotMoveable(CopyableButNotMoveable&& other) = delete;
    CopyableButNotMoveable& operator=(CopyableButNotMoveable&& other) = delete;

    int ma;
};

static inline bool operator==(CopyableButNotMoveable const& lhs, CopyableButNotMoveable const& rhs) noexcept
{
    return lhs.ma == rhs.ma;
}

static_assert(std::is_destructible<CopyableButNotMoveable>::value, "must be destructible");
static_assert(std::is_copy_constructible<CopyableButNotMoveable>::value, "must be copyable");
static_assert(std::is_copy_assignable<CopyableButNotMoveable>::value, "must be copyable");
static_assert(!std::is_move_constructible<CopyableButNotMoveable>::value, "must not be moveable");
static_assert(!std::is_move_assignable<CopyableButNotMoveable>::value, "must not be moveable");

static_assert(!std::is_trivially_destructible<CopyableButNotMoveable>::value, "must not be trivially destructible");
static_assert(!std::is_trivially_copyable<CopyableButNotMoveable>::value, "must not be trivially copyable");
static_assert(!std::is_trivially_copy_constructible<CopyableButNotMoveable>::value,
    "must not be trivially copy constructible");
static_assert(!std::is_trivially_copy_assignable<CopyableButNotMoveable>::value,
    "must not be trivially copy assignable");
static_assert(!std::is_trivially_move_constructible<CopyableButNotMoveable>::value,
    "must not be trivially move constructible");
static_assert(!std::is_trivially_move_assignable<CopyableButNotMoveable>::value,
    "must not be trivially move assignable");
static_assert(!std::is_trivial<CopyableButNotMoveable>::value, "must not be a trivial type");
static_assert(!std::is_literal_type<CopyableButNotMoveable>::value, "must not be a literal type");
static_assert(std::is_standard_layout<CopyableButNotMoveable>::value, "must have standard layout");

// -------------------------------------------------------------------------

// A type that is copy-constructible, but not copy-assignable
struct NonCopyAssignable final
{
    explicit NonCopyAssignable(int a) noexcept
        : ma(a)
    { }
    ~NonCopyAssignable() noexcept
    { }

    NonCopyAssignable(NonCopyAssignable const& other)
        : ma(other.ma)
    { }
    NonCopyAssignable& operator=(NonCopyAssignable const& other) = delete;

    NonCopyAssignable(NonCopyAssignable&& other) noexcept
        : ma(other.ma)
    { }
    NonCopyAssignable& operator=(NonCopyAssignable&& other) noexcept
    {
        ma = other.ma;
        return *this;
    }

    int ma;
};

static inline bool operator==(NonCopyAssignable const& lhs, NonCopyAssignable const& rhs) noexcept
{
    return lhs.ma == rhs.ma;
}

static_assert(std::is_destructible<NonCopyAssignable>::value, "must be destructible");
static_assert(std::is_copy_constructible<NonCopyAssignable>::value, "must be copyable");
static_assert(!std::is_copy_assignable<NonCopyAssignable>::value, "must not be copyable");
static_assert(std::is_move_constructible<NonCopyAssignable>::value, "must be moveable");
static_assert(std::is_move_assignable<NonCopyAssignable>::value, "must be moveable");

static_assert(!std::is_trivially_destructible<NonCopyAssignable>::value, "must not be trivially destructible");
static_assert(!std::is_trivially_copyable<NonCopyAssignable>::value, "must not be trivially copyable");
static_assert(!std::is_trivially_copy_constructible<NonCopyAssignable>::value,
    "must not be trivially copy constructible");
static_assert(!std::is_trivially_copy_assignable<NonCopyAssignable>::value, "must not be trivially copy assignable");
static_assert(!std::is_trivially_move_constructible<NonCopyAssignable>::value,
    "must not be trivially move constructible");
static_assert(!std::is_trivially_move_assignable<NonCopyAssignable>::value, "must not be trivially move assignable");
static_assert(!std::is_trivial<NonCopyAssignable>::value, "must not be a trivial type");
static_assert(!std::is_literal_type<NonCopyAssignable>::value, "must not be a literal type");
static_assert(std::is_standard_layout<NonCopyAssignable>::value, "must have standard layout");

// -------------------------------------------------------------------------

// A type that is non-copyable and non-move-assignable, but move-constructible
struct NonMoveAssignable final
{
    explicit NonMoveAssignable(int a) noexcept
        : ma(a)
    { }
    ~NonMoveAssignable() noexcept
    { }

    NonMoveAssignable(NonMoveAssignable const& other) = delete;
    NonMoveAssignable& operator=(NonMoveAssignable const& other) = delete;

    NonMoveAssignable(NonMoveAssignable&& other) noexcept
        : ma(other.ma)
    { }
    NonMoveAssignable& operator=(NonMoveAssignable&& other) = delete;

    int ma;
};

static inline bool operator==(NonMoveAssignable const& lhs, NonMoveAssignable const& rhs) noexcept
{
    return lhs.ma == rhs.ma;
}

static_assert(std::is_destructible<NonMoveAssignable>::value, "must be destructible");
static_assert(!std::is_copy_constructible<NonMoveAssignable>::value, "must not be copyable");
static_assert(!std::is_copy_assignable<NonMoveAssignable>::value, "must not be copyable");
static_assert(std::is_move_constructible<NonMoveAssignable>::value, "must be moveable");
static_assert(!std::is_move_assignable<NonMoveAssignable>::value, "must be moveable");

static_assert(!std::is_trivially_destructible<NonMoveAssignable>::value, "must not be trivially destructible");
static_assert(!std::is_trivially_copyable<NonMoveAssignable>::value, "must not be trivially copyable");
static_assert(!std::is_trivially_copy_constructible<NonMoveAssignable>::value,
    "must not be trivially copy constructible");
static_assert(!std::is_trivially_copy_assignable<NonMoveAssignable>::value, "must not be trivially copy assignable");
static_assert(!std::is_trivially_move_constructible<NonMoveAssignable>::value,
    "must not be trivially move constructible");
static_assert(!std::is_trivially_move_assignable<NonMoveAssignable>::value, "must not be trivially move assignable");
static_assert(!std::is_trivial<NonMoveAssignable>::value, "must not be a trivial type");
static_assert(!std::is_literal_type<NonMoveAssignable>::value, "must not be a literal type");
static_assert(std::is_standard_layout<NonMoveAssignable>::value, "must have standard layout");

// -------------------------------------------------------------------------

// A type that is non-copyable and non-move-assignable, but move-constructible (but not noexcept)
struct NotNothrowMoveConstructible final
{
    explicit NotNothrowMoveConstructible(int a) noexcept
        : ma(a)
    { }
    ~NotNothrowMoveConstructible() noexcept
    { }

    NotNothrowMoveConstructible(NotNothrowMoveConstructible const& other)
        : ma(other.ma)
    { }
    NotNothrowMoveConstructible& operator=(NotNothrowMoveConstructible const& other)
    {
        ma = other.ma;
        return *this;
    }

    NotNothrowMoveConstructible(NotNothrowMoveConstructible&& other)
        : ma(other.ma)
    { }
    NotNothrowMoveConstructible& operator=(NotNothrowMoveConstructible&& other)
    {
        ma = other.ma;
        return *this;
    }

    int ma;
};

static inline bool operator==(NotNothrowMoveConstructible const& lhs, NotNothrowMoveConstructible const& rhs) noexcept
{
    return lhs.ma == rhs.ma;
}

static_assert(std::is_destructible<NonMoveAssignable>::value, "must be destructible");
static_assert(std::is_copy_constructible<NotNothrowMoveConstructible>::value, "must be copyable");
static_assert(std::is_copy_assignable<NotNothrowMoveConstructible>::value, "must be copyable");
static_assert(std::is_move_constructible<NotNothrowMoveConstructible>::value, "must be moveable");
static_assert(std::is_move_assignable<NotNothrowMoveConstructible>::value, "must be moveable");

static_assert(!std::is_nothrow_copy_constructible<NotNothrowMoveConstructible>::value, "must not be nothrow copyable");
static_assert(!std::is_nothrow_copy_assignable<NotNothrowMoveConstructible>::value, "must not be nothrow copyable");
static_assert(!std::is_nothrow_move_constructible<NotNothrowMoveConstructible>::value,
    "must not be nothrow move-constructible");
static_assert(!std::is_nothrow_move_assignable<NotNothrowMoveConstructible>::value,
    "must not be nothrow move-assignable");

static_assert(!std::is_trivially_destructible<NotNothrowMoveConstructible>::value,
    "must not be trivially destructible");
static_assert(!std::is_trivially_copyable<NotNothrowMoveConstructible>::value, "must not be trivially copyable");
static_assert(!std::is_trivially_copy_constructible<NotNothrowMoveConstructible>::value,
    "must not be trivially copy constructible");
static_assert(!std::is_trivially_copy_assignable<NotNothrowMoveConstructible>::value,
    "must not be trivially copy assignable");
static_assert(!std::is_trivially_move_constructible<NotNothrowMoveConstructible>::value,
    "must not be trivially move constructible");
static_assert(!std::is_trivially_move_assignable<NotNothrowMoveConstructible>::value,
    "must not be trivially move assignable");
static_assert(!std::is_trivial<NotNothrowMoveConstructible>::value, "must not be a trivial type");
static_assert(!std::is_literal_type<NotNothrowMoveConstructible>::value, "must not be a literal type");
static_assert(std::is_standard_layout<NotNothrowMoveConstructible>::value, "must have standard layout");

#endif  // NOSAL_CORE_TEST_TYPES_H
